package com.ruoyi.aitcommon.utils;

import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidPooledConnection;
import org.springframework.stereotype.Component;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.ruoyi.aitcommon.vo.DBConVO;

/**
 * Druid数据库连接池管理
 * @author Administrator
 */
@Component
public class DruidUtils {
	//ConcurrentHashMap线程安全map
	private static final Map<String, DruidDataSource> dsMap = new ConcurrentHashMap<>();

//	构造函数
	@PostConstruct
	public void init() {
//		System.out.println("DruidUtils init==============");
	}
//	析构函数
	@PreDestroy
	public void destroy() {
//		System.out.println("DBConManager destroy================");
		for (Entry<String, DruidDataSource> entry : dsMap.entrySet()) {
			DruidDataSource ds = (DruidDataSource)entry.getValue();
			LogUtils.info("dataSource: "+ds.getUrl());
	        ds.close();
	    }
		dsMap.clear(); // 清空 map
	}
	
	/**
	 * 从连接池中获取数据库链接
	 * 参考链接:https://blog.csdn.net/weixin_43520450/article/details/107231118
	 * 该链接释放后自动归还至连接池
	 *
	 * @param vo 数据库链接VO
	 * @return 数据库链接
	 */
	public static DruidPooledConnection getConnection(DBConVO vo) throws Exception {
		final DruidDataSource ds = getDataSource(vo);
		//数据库重启后会报错: Connection reset by peer: socket write error
		//第二次,会再次尝试获取数据库链接
		final DruidPooledConnection conn = ds.getConnection();
		//活跃连接数>最大连接数-5时,记录日志
		if(ds.getActiveCount()>ds.getMaxActive()-5)
			LogUtils.info("打开链接:[{}],活跃连接数:[{}]", vo.getUrl(),ds.getActiveCount());
		return conn;
	}
	/**
	 * 获取数据库连接池
	 * 如果没有,创建
	 * synchronized 避免数据源的重复创建
	 * @param vo 链接参数
	 * @return 数据库连接池
	 */
	public static synchronized DruidDataSource getDataSource(DBConVO vo) throws Exception {
		if (vo == null || ObjUtils.isBlank(vo.getUrl()))
			throw new Exception("传入DBConVO为空或URL为空!");

		DruidDataSource ds;
		if(dsMap.containsKey(vo.getUrl())) {
			ds = dsMap.get(vo.getUrl());
		}else{
			//使用Druid连接池
			ds = (DruidDataSource)DruidDataSourceFactory.createDataSource(vo.toDruidMap());
			LogUtils.info("创建新的数据源: [{}]", vo.getUrl());
			//Druid参数配置
			ds.setInitialSize(5);// 初始化时建立的物理连接数量（应用启动时就创建的连接数）
			ds.setMaxActive(20);// 连接池中最大活跃连接数（最大并发使用的连接数量）
			ds.setMinIdle(5);// 连接池中最小空闲连接数（即使没人用，也保持至少这么多连接）
			ds.setMaxWait(5000);// 从连接池获取连接的最大等待时间（单位：毫秒），超时抛出异常 例如 60000 = 60秒，避免线程无限等待
			ds.setTimeBetweenConnectErrorMillis(30000); // 连接失败后，等待30秒再重试创建
			ds.setConnectionErrorRetryAttempts(3);      // 连接失败时重试3次（部分版本支持）
			ds.setBreakAfterAcquireFailure(false);      // 是否在获取失败后中断（false=继续重试）
			// 启用保活和检测
			// 是否开启连接保活机制（Druid 特有功能）
			// 对于长时间空闲的连接，会自动发送 validationQuery 检测并尝试修复
			// 防止连接因超时被数据库关闭，推荐开启
			ds.setKeepAlive(true);
			// 在空闲时检测连接是否有效（配合 timeBetweenEvictionRunsMillis 使用）
			// 可以及时发现并清理断开的连接（如数据库重启后）
			ds.setTestWhileIdle(true);
			// 连接池检测空闲连接的运行周期（单位：毫秒）
			// 每隔 60 秒运行一次空闲连接清理/检测任务
			ds.setTimeBetweenEvictionRunsMillis(60000);
			// 连接在连接池中最小空闲时间（单位：毫秒）
			// 超过这个时间的空闲连接才可能被清理
			// 300000 = 5分钟，避免刚空闲的连接被误删
			ds.setMinEvictableIdleTimeMillis(300000);
			// 在从连接池获取连接时检测其有效性
			// 开启会影响性能（每次获取都执行一次 SQL），一般不建议开启
			ds.setTestOnBorrow(false);
			// 在将连接归还给连接池时检测其有效性
			// 同样会影响性能，通常关闭
			ds.setTestOnReturn(false);
			// 用于检测连接是否有效的 SQL 查询语句
			// MySQL 常用 SELECT 1，Oracle 用 SELECT 1 FROM DUAL
			// 必须设置，否则无法验证连接有效性
			if(vo.isORACLE()){
				ds.setValidationQuery("SELECT 1 FROM DUAL");
			}else{
				ds.setValidationQuery("SELECT 1");
			}

			dsMap.put(vo.getUrl(), ds);
		}
		return ds;
	}
}
